<?php
/*
 * Created on 23.12.2005
 *
 */
 
/**
 * Lexer for the psp EL language
 */
class ELLexer 
{

    var $code;
    var $tokenlist;
    var $inString = false;
    var $line = 1;
    var $strlen = 0;
    // start
    var $s = 0;
    // counter
    var $i = 0;
    // pointer for nextToken
    var $tokenpointer = 0;

    function ELLexer ($code) 
    {
        $this->code = $code;
    }
    
    function hasNextToken()
    {
        return (isset($this->tokenlist[$this->tokenpointer]));
    }
    
    function nextToken()
    {
        return $this->tokenlist[$this->tokenpointer++];
    }
    
    function lookahead($i)
    {
        return $this->tokenlist[$this->tokenpointer+$i];
    }
    
    function rewind ()
    {
        $this->tokenpointer = 0;
    }
    
    function char ($c)
    {
        return (preg_match("/\w/", $c));
    }
    
    function lex () {
        $this->strlen = strlen($this->code);
        $inOuterString = true;
        for ($this->i = 0; $this->i < $this->strlen; $this->i++) 
        {
            $token = array();
            $this->s = $this->i;
            if ($inOuterString)
            {
                if ($this->code[$this->i] == '$' && $this->code[$this->i+1] == '{')
                {
                    $inOuterString = false;
                    $this->t('${', 'codeopen');
                }
                else
                {
                    $this->eatOuterString();                    
                    $inOuterString = false;
                }
            }
            else
            {
                if (($this->code[$this->i] == '0' && !preg_match("/[1-9]/", $this->code[$this->i+1])) 
                    || preg_match("/[1-9]/", $this->code[$this->i]))
                {
                    $this->digit();
                }
                elseif ($this->code[$this->i] == "'")
                {
                    $this->i++;
                    $this->s = $this->i;
                    $this->eatString();
                }
                elseif ($this->code[$this->i] == '$')
                {
                    if ($this->code[$this->i+1] == '{')
                    {
                        $inOuterString = false;
                        $this->t('${', 'codeopen');
                    }
                    else
                        $this->t('$', 'dollar');
                     
                }
                elseif ($this->code[$this->i] == '}')
                {
                    $this->t('}', 'rcurlybrace');
                    $inOuterString = true;
                }
                elseif ($this->code[$this->i] == ']')
                {
                    $this->t(']', 'rbracket');
                }
                elseif ($this->code[$this->i] == '[')
                {
                    $this->t('[', 'lbracket');
                }
                elseif ($this->code[$this->i] == '(')
                {
                    $this->t('(', 'lpar');
                }
                elseif ($this->code[$this->i] == '%')
                {
                    $this->t('%', 'mod');
                }
                elseif ($this->code[$this->i] == '|')
                {
                    if ($this->code[$this->i+1] == '|')
                    {
                        $this->t('||', 'or');
                    }
                    else
                        $this->t('|', 'eor');
                }
                elseif ($this->code[$this->i] == '&')
                {
                    if ($this->code[$this->i+1] == '&')
                    {
                        $this->t('&&', 'and');
                    }
                    else
                        $this->t('&', 'eand');
                }
                elseif ($this->code[$this->i] == ')')
                {
                    $this->t(')', 'rpar');
                }
                elseif ($this->code[$this->i] == '+')
                {
                    $this->t('+', 'plus');
                }
                elseif ($this->code[$this->i] == '.')
                {
                    $this->t('.', 'concat');
                }
                elseif ($this->code[$this->i] == ',')
                {
                    $this->t(',', 'comma');
                }
                elseif ($this->code[$this->i] == '-')
                {
                    if ($this->code[$this->i+1] == '>')
                    {
                        $this->t('->', 'arrow');
                    }
                    else
                        $this->t('-', 'minus');
                }
                elseif ($this->code[$this->i] == '*')
                {
                    $this->t('*', 'mult');
                }
                elseif ($this->code[$this->i] == '/')
                {
                    $this->t('/', 'div');
                }
                elseif ($this->code[$this->i] == '=')
                {
                    if ($this->code[$this->i+1] == '=')
                    {
                        $this->t('==', 'eq');
                    }
                    else
                    {
                        $this->t('=', 'assign');
                    }
                }
                elseif ($this->code[$this->i] == '<')
                {
                    if ($this->code[$this->i+1] == '=')
                    {
                        $this->t('<=', 'leq');
                    }
                    else
                        $this->t('<', 'lt');
                }
                elseif ($this->code[$this->i] == '>')
                {
                    if ($this->code[$this->i+1] == '=')
                    {
                        $this->t('>=', 'geq');
                    }
                    else
                        $this->t('>', 'gt');
                }
                elseif ($this->code[$this->i] == '!')
                {
                    if ($this->code[$this->i+1] == '=')
                    {
                        $this->t('!=', 'neq');
                    }
                    else
                        $this->t('!', 'not');
                }
                elseif (preg_match("/[A-Za-z]/", $this->code[$this->i]))
                {
                    if ($this->fn($this->i))
                    {
                        $this->t('fn:', 'fun');
                    }
                    elseif ($this->findKeyword($this->i, "false"))
                    {
                        $this->t('false', 'false');
                    }
                    elseif ($this->findKeyword($this->i, "true"))
                    {
                        $this->t('true', 'true');
                    }
                    else
                        $this->identifier();
                }
                elseif (preg_match("/\s/", $this->code[$this->i]))
                {
                    $this->eatWhitespaces();
                }
                else
                    die ("Unexpected character '".$this->code[$this->i]."' in line ".$this->line.":".($this->i+1)."\n");
            }
            $this->s = $this->i;
        }
        // push eof token to the stack
        $token = array('type' => 'eof', 'value' => 'eof');
        $this->tokenlist[] = $token;
    }           
    
    function findKeyword($i, $str)
    {
        $strlen = strlen($str);
        for ($c = 0; $c < $strlen; $c ++)
        {
            if ($str[$c] != $this->code[$i + $c])
                return false; 
        }
        if (preg_match('/\w/', $this->code[$i + $strlen]))
            return false;
        return true;
    }

    function fn($i)
    {
        $str = 'fn:';
        $strlen = strlen($str);
        for ($c = 0; $c < $strlen; $c ++)
        {
            if ($str[$c] != $this->code[$i + $c])
                return false; 
        }
        if (preg_match('/[A-Za-z\s]/', $this->code[$i + $strlen]))
            return true;
        return true;
    }

    function binOp ($op, $ass = '')
    {
        if ($ass)
            $token['type'] = 'T_'.$ass.'_BIN_OP';
        else
            $token['type'] = 'T_BIN_OP';
        $token['value'] = $op;
        $token['line'] = $this->line;
        $token['charno'] = $this->s;
        $this->tokenlist[] = $token;
        $this->i += strlen($op) - 1;
    }

    function t ($value, $type)
    {
        $token['type']  = $type;
        $token['value'] = $value;
        $token['line'] = $this->line;
        $token['charno'] = $this->i;
        $this->tokenlist[] = $token;
        $this->i += strlen($value) - 1;
    }

    function compOp ($op)
    {
        $token['type'] = 'T_COMP_OP';
        $token['value'] = $op;
        $token['line'] = $this->line;
        $token['charno'] = $this->s;
        $this->tokenlist[] = $token;
        $this->i += strlen($op) - 1;
    }

    function keyword ($type)
    {
        $token['type'] = $type;
        $token['line'] = $this->line;
        $token['charno'] = $this->s;
        $this->tokenlist[] = $token;
    }

    function unOp ($side, $op)
    {
        $token['type'] = 'T_'.$side.'_OP';
        $token['value'] = $op;
        $token['line'] = $this->line;
        $token['charno'] = $this->s;
        $this->tokenlist[] = $token;
        $this->i += strlen($op) - 1;
    }

    function eatWhitespaces ()
    {
        $cachei = $this->i;
        while ($this->code[$this->i] == "\n" || $this->code[$this->i] == "\r" 
            || $this->code[$this->i] == "\t" || $this->code[$this->i] == ' ')
        {
            if ($this->code[$this->i] == "\n")
                $this->line++;
            $this->i++;
        }
        if ($this->i - $cachei > 0)
        {
            $this->i--;
        }
    }
    
    function identifier ()
    {
        $token = array();
        while (preg_match("/[A-Za-z0-9_]/", $this->code[$this->i]))
        {
            $this->i++;
        }
        if ($this->i-$this->s)
        {
            $token['type'] = 'id';
            $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
            $token['line'] = $this->line;
            $this->tokenlist[] = $token;
            $this->s = $this->i;
            $this->i--;
            return true;
        }
        $this->i--;
        return false;
    }

    function digit ()
    {
        $token = array();
        while (preg_match("/[0-9]/", $this->code[$this->i]))
        {
            $this->i++;
        }
        if ($this->code[$this->i] == '.')
            $this->i++;
        while (preg_match("/[0-9]/", $this->code[$this->i]))
        {
            $this->i++;
        }
        if ($this->i-$this->s)
        {
            $token['type'] = 'digit';
            $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
            $token['line'] = $this->line;
            $token['charno'] = $this->s;
            $this->tokenlist[] = $token;
            $this->s = $this->i;
            $this->i--;
            return true;
        }
        $this->i--;
        return false;
    }

    function eatString ()
    {
        $token = array();
        while (($this->code[$this->i] != "'" || $this->code[$this->i-1] == "\\") && $this->i < $this->strlen)
        {
            $this->i++;
        }
        if ($this->i-$this->s)
        {
            $token['type'] = 'string';
            $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
            $token['line'] = $this->line;
            $token['charno'] = $this->s;
            $this->tokenlist[] = $token;
            $this->s = $this->i;

            return true;
        }
        return false;
    }

    function eatOuterString ()
    {
        $token = array();
        while ((($this->code[$this->i] != "$") || ($this->code[$this->i+1] != "{")) && $this->i < $this->strlen)
        {
            $this->i++;
        }
        if ($this->i-$this->s)
        {
            $token['type'] = 'outerstring';
            $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
            $token['line'] = $this->line;
            $token['charno'] = $this->s;
            $this->tokenlist[] = $token;
            $this->i--;
            $this->s = $this->i;

            return true;
        }
        return false;
    }

}

?>
